package com.easygroup.ngarihealth.plugin

import com.android.build.api.transform.DirectoryInput
import com.android.build.api.transform.Format
import com.android.build.api.transform.JarInput
import com.android.build.api.transform.TransformOutputProvider
import com.easygroup.ngarihealth.easymodule.ModuleCollection
import com.easygroup.ngarihealth.easymodule.ModuleLifecycleCallback
import com.easygroup.ngarihealth.easymodule.annotation.Module
import groovy.io.FileType
import javassist.ClassPath
import javassist.ClassPool
import javassist.CtClass
import org.gradle.api.Project
import org.apache.commons.io.FileUtils
import java.util.jar.JarEntry
import java.util.jar.JarFile

/**
 * Created by fuxiaohua on 2021/5/10 0010.
 */
class Inject {

    private static final String COLLECT_CLASS_ENTRY = ModuleCollection.class.name
    private final Project project
    private final ClassPool classPool
    private TreeSet<String> dirs = new TreeSet<>()
    private TreeSet<String> jarFiles = new TreeSet<>()
    /**
     * Key,entry 存放EasyModule
     */
    private Map<String, EasyModule> moduleMap = new HashMap<>()
    private HashMap<String, JarEntry> jarEntryHashMap
    private String targetPath = null

    Inject(Project project, ClassPool classPool) {
        this.project = project
        this.classPool = classPool
        moduleMap.clear()
    }

    // 第一处 扫描class文件夹
    void collectClassFile(File dst) {
        // 如果是是个文件,直接返回
        if (!dst.isDirectory()) return
        // 将文件夹添加到类路径
        def classPath = classPool.appendClassPath(dst.path)
        // 扫描每个文件
        dst.eachFileRecurse {
            // 如果是类文件, 则扫描
            if (isClassFile(it.path)) {
                def entry = it.path.substring(dst.path.length() + 1, it.path.lastIndexOf("."))
                        .replace("\\", ".")
                if (entry.contains("luoyubo")) {
                    log("在第一处中找到------->>$entry")
                    log("在第一处中找到的位置------->>$dst")
                    log("在第一处中找到的classpath------->>$classPath")
                }
                parseAnnotation(dst.path, entry)
            }
        }
        classPool.removeClassPath(classPath)
    }

    // 第二处 扫描jar文件
    void collectClassJarFile(File dst) {
        // 如果如果dst不包含ModuleCollection类, 继续扫描
        def targetFile = findTargetJar(dst)
        if (targetFile != null) {
            // 如果dst包含ModuleCollection类,直接返回, 找到目标文件
            targetPath = targetFile.path
            return
        }
        def jarFile = new JarFile(dst)
        def entries = jarFile.entries()
        def classPath = classPool.appendClassPath(dst.path)
        // 对jar中的每个文件扫描
        while (entries.hasMoreElements()) {
            def element = entries.nextElement()
            // 如果是个文件夹 跳过 扫里面的
            if (element.isDirectory()) continue
            // 如果不是类文件跳过
            if (!isClassFile(element.name)) continue
            def entry = element.name.substring(0, element.name.lastIndexOf(".")).replace("/", ".")
            if (entry.contains("luoyubo")) {
                log("在第二处中找到------->>$entry")
                log("位置------->>$dst")
                log("在第二处中找到的classpath------->>$classPath")
            }
            parseAnnotation(dst.path, entry)
        }
        jarFile.close()
        classPool.removeClassPath(classPath)
    }

    void setDirs(TransformOutputProvider provider, DirectoryInput input) {
        def file = provider.getContentLocation(input.name, input.contentTypes, input.scopes, Format.DIRECTORY)
        dirs.add(file.path)
    }

    void setJars(TransformOutputProvider provider, JarInput input) {
        def file = provider.getContentLocation(Utils.getJarHexName(input, false), input.contentTypes, input.scopes, Format.JAR)
        jarFiles.add(file.parentFile.path)
    }

    // 第三出 处理
    void process() {
        dirs.parallelStream().each { path ->
            def dir = new File(path)
            if (dir.isDirectory()) {
                classPool.appendClassPath(dir)
                dir.eachFileRecurse {
                    if (isClassFile(it.path)) {
                        def entry = it.path.substring(path.length() + 1, it.path.lastIndexOf("."))
                                .replace("\\", ".")
                        if (entry.contains("luoyubo")) {
                            log("在第三处1中找到------->>$entry")
                        }
                        parseAnnotation(dir, entry)
                    }
                }
            }
        }
        jarFiles.parallelStream().each {
            def dir = new File(it)
            if (dir.isDirectory()) {
                dir.eachFileRecurse(FileType.FILES) {
                    if (it.name.endsWith(".jar")) {
                        def collect = findTargetJar(it)
                        if (collect != null) {
                            targetPath = collect.path
                            return
                        }
                        ClassPath classPath = classPool.appendClassPath(it.path)
                        JarFile jarFile = new JarFile(it.path)
                        Enumeration<JarEntry> entries = jarFile.entries()
                        while (entries.hasMoreElements()) {
                            JarEntry jarEntry = entries.nextElement()
                            if (jarEntry.isDirectory()) continue
                            if (!isClassFile(jarEntry.name)) continue
                            String entry = jarEntry.name.substring(0, jarEntry.name.lastIndexOf(".")).replace("/", ".")
                            if (entry.contains("luoyubo")) {
                                log("在第三处2中找到------->>$entry")
                            }
                            parseAnnotation(it.path, entry)
                        }
                        jarFile.close()
                        // classPool.removeClassPath(classPath)
                    }
                }
            }
        }
        pack(injectCode())
    }

    private void pack(outFile) {
        if (targetPath == null || outFile == null) return
        Utils.zipJar(outFile, new File(targetPath), jarEntryHashMap)
        jarEntryHashMap.clear()
        FileUtils.deleteDirectory(outFile)
    }

    private File injectCode() {
        log("Target: " + targetPath)
        if (targetPath == null) return null
        def jarFile = new JarFile(targetPath)
        def entries = jarFile.entries()
        jarEntryHashMap = new HashMap<String, JarEntry>()
        while (entries.hasMoreElements()) {
            def element = entries.nextElement()
            if (!element.isDirectory()) {
                jarEntryHashMap.put(element.name, element)
            }
        }
        jarFile.close()
        if (jarEntryHashMap.isEmpty()) return null

        def outPath = targetPath.replace(".jar", "")
        List list = Utils.unzipJar(targetPath, outPath)
        def file = new File(targetPath)
        def delete = file.delete()
        if (!delete) {
            log("Target file was not deleted..")
        }
        def classPath = classPool.appendClassPath(outPath)
        list.each {
            // 如果是集合类, 生成添加元素代码
            if (it.endsWith(".class") && it == COLLECT_CLASS_ENTRY + ".class") {
                def clazz = classPool.getCtClass(COLLECT_CLASS_ENTRY)
                if (clazz.isFrozen())
                    clazz.defrost()
                clazz.getDeclaredMethod("init").setBody(genCode())
                clazz.writeFile(outPath)
                clazz.detach()
            }
        }
        classPool.removeClassPath(classPath)
        return new File(outPath)
    }

    /**
     * 生成代码 将EasyModule添加到ModuleCollection集合里面去
     * @return
     */
    private String genCode() {
        StringBuilder sb = new StringBuilder()
        sb.append("{\n")
        moduleMap.each {
            classPool.appendClassPath(it.value.classPath)
            classPool.importPackage(it.value.packageName)
            sb.append("this.add(\"")
                    .append(it.value.key)
                    .append("\", (${ModuleLifecycleCallback.class.simpleName}) new ")
                    .append(it.value.entry)
                    .append("());\n")
        }
        sb.append("}\n")
        return sb.toString()
    }

    /**
     * parse the entry has annotation the 'Module'
     * @param path the path to find class
     * @param entry like 'com.company.lang.xx.xxModule' entry == clazz.name
     */
    private void parseAnnotation(String path, String entry) {
        CtClass clazz = null
        if (entry.contains("luoyubo")) {
            log("找到了--------->> $entry")
        }
        try {
            clazz = classPool.getOrNull(entry)
            if (entry.contains("luoyubo")) {
                log("clazz是--------->> $clazz")
            }
            if (clazz != null) {
                def annotation = clazz.getAnnotation(Module.class)
                if (annotation != null) {
                    Module module = annotation as Module
                    def value = module.value()
                    def packageName = clazz.packageName
                    moduleMap.put(value, new EasyModule(path, value, entry, packageName))
                    log("Class <$entry> is annotationed.")
                }
            }
        } catch (Exception ignored) {
            // if (entry.contains("MainModuleLifeCycle")) {
            log("main发生异常-------->> ${ignored.getMessage()}")
            // }
        } finally {
            if (clazz != null) {
                clazz.detach()
            }
        }
    }

    private static boolean isClassFile(String filePath) {
        return filePath.endsWith(".class") &&
                !filePath.contains('$') &&
                !filePath.contains('R.class') &&
                !filePath.contains("BuildConfig.class") &&
                !filePath.contains("META-INF")
    }

    /**
     * @param file 当File是jar文件,并且含有ModuleCollection类, 那么返回这个file,否则返回空
     * @return
     */
    private static File findTargetJar(File file) {
        if (!file.name.endsWith(".jar")) return null
        JarFile jarFile = new JarFile(file)
        if (jarFile.getJarEntry(ModuleCollection.class.name.replace(".", "/") + ".class") != null) {
            jarFile.close()
            return file
        }
        jarFile.close()
        return null
    }

    private void log(String msg, Object... args) {
        String text = String.format(msg, args)
        project.getLogger().error("[ModuleTransform]:${text}")
    }

}